/****************************************************************************

 Copyright (C) 2009 Jean Pierre Charalambos. All rights reserved.

 Jean Pierre Charalambos (http://dis.unal.edu.co/profesores/pierre/) is an
 Affiliate Associate Professor of the Faculty of Engineering at National
 University of Colombia (http://www.unal.edu.co/).
 
 Shader configurarion by Juan Carlos León (http://code.google.com/p/maku/).

 This file is part of maku version 0.2.

 http://code.google.com/p/maku/
 email: jpcharalambosh@unal.edu.co

 This file may be used under the terms of the GNU General Public License
 versions 2.0 or 3.0 as published by the Free Software Foundation and
 appearing in the COPYING file included in the packaging of this file.

 This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

*****************************************************************************/

//TODO shaderConfigAct hotkey and connect to a slot

#include "scenebase.h"
#include "uniform.h"

#include <math.h>
#include <iostream>

#include <QAction>
#include <QMenu>
#include <QDir>

void SceneBase::draw() const {
#ifdef WITH_GLEW
    if ( hasOpenGL2 )
        glUseProgram ( p );
#endif
    drawMesh();
#ifdef WITH_GLEW
    if ( hasOpenGL2 )
        glUseProgram ( 0 );
#endif
}

#ifdef WITH_GLEW
void SceneBase::setShader ( const QString & shader )  {
    //OpenGL es flexible aca: no importa si los shaders no han sido creados ;-)
    glDetachShader ( p,v );
    glDetachShader ( p,f );
    glDeleteShader ( v );
    glDeleteShader ( f );
    glDeleteProgram ( p );
    if ( shader != "fixed functionality" ) {
        p = glCreateProgram();
        //glUseProgram(p);
        v = glCreateShader ( GL_VERTEX_SHADER );
        f = glCreateShader ( GL_FRAGMENT_SHADER );
        QString dir = "/usr/share/maku/shaders/";
        QDir shaderDir ( dir );
        if ( shaderDir.count() == 0 ) {
            dir = "/usr/local/share/maku/shaders/";
            shaderDir.setPath ( dir );
            if ( shaderDir.count() == 0 ) {
                dir = shaderDir.homePath();
                dir = shaderDir.toNativeSeparators ( dir );
                dir = dir + "/share/maku/shaders/";
                shaderDir.setPath ( dir );
                if ( shaderDir.count() == 0 ) {
                    dir = "shaders/";
                    shaderDir.setPath ( dir );
                    if ( shaderDir.count() == 0 ) {
                        //std::cout<< "Didn't found shaders directory"<<std::endl;
                        exit ( 1 );
                    }
                }
            }
        }
        QString vertexExtension = ".vert";
        QString fragmentExtension = ".frag";
        QString vertexShader = dir + shader + vertexExtension;
        QString fragmentShader = dir + shader + fragmentExtension;
        //2.1 Convert it to the right encoding:
        QByteArray ba1= ( readShaderCode ( vertexShader ) ).toLocal8Bit();
        QByteArray ba2= ( readShaderCode ( fragmentShader ) ).toLocal8Bit();
        //2.2 Allocate enough memory:
        vs=new char[ba1.size() + 1];
        fs=new char[ba2.size() + 1];
        //2.3 Copy them:
        strcpy ( vs,  ba1.data() );
        strcpy ( fs,  ba2.data() );

        const char * vv = vs;
        const char * ff = fs;

        glShaderSource ( v, 1, &vv,NULL );
        glShaderSource ( f, 1, &ff,NULL );

        free ( vs );
        free ( fs );

        glAttachShader ( p,v );
        glAttachShader ( p,f );

        glCompileShader ( v );
        glCompileShader ( f );

        glLinkProgram ( p );

        uniforms.clear();
        extractUniforms();
    }
    //emit changed();
}

QVector<Uniform> SceneBase::getCurrentUniforms() {
    return uniforms;
}

bool SceneBase::uniformAlreadyProcessed ( QString name ) {
    QVectorIterator<Uniform> iter ( uniforms );
    while ( iter.hasNext() ) {
        Uniform tempUniform=iter.next();
        if ( name.compare ( tempUniform.getName() ) ==0 )
            return true;
    }
    return false;
}

void SceneBase::extractUniforms() {
    //GLint program=p;
    GLint  uniformAmount= 0;
    glGetProgramiv ( p,GL_ACTIVE_UNIFORMS,&uniformAmount );//get amount of Uniform variables

    char name[ 256 ] = "\0";
    GLsizei length = 0;
    GLint size = 0;
    GLenum type = 0;
    for ( int i=0;i<uniformAmount;i++ ) {
        glGetActiveUniform ( p, i, sizeof ( name ), &length, &size, &type, name );
        QString uniformName;
        uniformName.append ( name );
        if ( ! ( name[0]=='g' && name[1]=='l' && name[2]=='_' ) && !uniformAlreadyProcessed ( uniformName ) ) {//ONLY user defined uniforms that are not alredy on the uniform array (due to the 4x call of setShader on mainWindow.cpp
            int location=glGetUniformLocation ( p,name );
            if ( type == GL_FLOAT ) {
                GLfloat tempFloat;
                glGetUniformfv ( p,location,&tempFloat );
                Uniform anUniform ( uniformName,tempFloat,1,location );
                uniforms.append ( anUniform );
                //std::cout<<"Loaction as created "<<location<<std::endl;
                std::cout<<"uniform "<< uniformName.toStdString() <<" type is float"<<" value "<<tempFloat<<std::endl;
            } else if ( type == GL_FLOAT_VEC2 ) {
                GLfloat tempFloat;
                glGetUniformfv ( p,location,&tempFloat );
                GLfloat* iter=&tempFloat;
                Uniform anUniform ( uniformName,*iter,2,location );
                iter++;
                anUniform.setFloatValue ( *iter,2 );
                uniforms.append ( anUniform );
                // std::cout<<"uniform "<< uniformName.toStdString()<<" type is float 2d"<<"Values: "<<anUniform.getValueAsFloat(1)<<" "<<anUniform.getValueAsFloat(2)<<std::endl;
            } else if ( type == GL_FLOAT_VEC3 ) {
                GLfloat tempFloat;
                glGetUniformfv ( p,location,&tempFloat );
                GLfloat* iter=&tempFloat;
                Uniform anUniform ( uniformName,*iter,3,location );
                iter++;
                anUniform.setFloatValue ( *iter,2 );
                iter++;
                anUniform.setFloatValue ( *iter,3 );
                uniforms.append ( anUniform );
                //std::cout<<"uniform "<< uniformName.toStdString()<<" type is float 2d"<<"Values: "<<anUniform.getValueAsFloat(1)<<" "<<anUniform.getValueAsFloat(2)<<std::endl;
            } else if ( type == GL_FLOAT_VEC4 ) {
                GLfloat tempFloat;
                glGetUniformfv ( p,location,&tempFloat );
                GLfloat* iter=&tempFloat;
                Uniform anUniform ( uniformName,*iter,4,location );
                iter++;
                anUniform.setFloatValue ( *iter,2 );
                iter++;
                anUniform.setFloatValue ( *iter,3 );
                iter++;
                anUniform.setFloatValue ( *iter,4 );
                uniforms.append ( anUniform );
                // std::cout<<"uniform "<< uniformName.toStdString()<<" type is float 2d"<<"Values: "<<anUniform.getValueAsFloat(1)<<" "<<anUniform.getValueAsFloat(2)<<std::endl;
            } else if ( type == GL_INT ) {
                GLint tempInt;
                glGetUniformiv ( p,location,&tempInt );
                Uniform anUniform ( uniformName,tempInt,1,location );
                uniforms.append ( anUniform );
                //std::cout<<"uniform "<< uniformName.toStdString()<<" type is int"<<std::endl;
            } else if ( type == GL_INT_VEC2 ) {
                GLint tempInt;
                glGetUniformiv ( p,location,&tempInt );
                GLint* iter=&tempInt;
                Uniform anUniform ( uniformName,*iter,2,location );
                iter++;
                anUniform.setIntValue ( *iter,2 );
                uniforms.append ( anUniform );
                // std::cout<<"uniform "<< uniformName.toStdString()<<" type is int 2d"<<"Values: "<<anUniform.getValueAsFloat(1)<<" "<<anUniform.getValueAsFloat(2)<<std::endl;
            } else if ( type == GL_INT_VEC3 ) {
                GLint tempInt;
                glGetUniformiv ( p,location,&tempInt );
                GLint* iter=&tempInt;
                Uniform anUniform ( uniformName,*iter,3,location );
                iter++;
                anUniform.setIntValue ( *iter,2 );
                iter++;
                anUniform.setIntValue ( *iter,3 );
                uniforms.append ( anUniform );
                //std::cout<<"uniform "<< uniformName.toStdString()<<" type is int 2d"<<"Values: "<<anUniform.getValueAsFloat(1)<<" "<<anUniform.getValueAsFloat(2)<<std::endl;
            } else if ( type == GL_INT_VEC4 ) {
                GLint tempInt;
                glGetUniformiv ( p,location,&tempInt );
                GLint* iter=&tempInt;
                Uniform anUniform ( uniformName,*iter,4,location );
                iter++;
                anUniform.setIntValue ( *iter,2 );
                iter++;
                anUniform.setIntValue ( *iter,3 );
                iter++;
                anUniform.setIntValue ( *iter,4 );
                uniforms.append ( anUniform );
                // std::cout<<"uniform "<< uniformName.toStdString()<<" type is int 2d"<<"Values: "<<anUniform.getValueAsFloat(1)<<" "<<anUniform.getValueAsFloat(2)<<std::endl;
            } else
                std::cout<<"uniform "<< uniformName.toStdString() <<" type is Unknown"<<std::endl;
        }
    }
    std::cout<<std::endl;
}

void SceneBase::applyXvalueChange ( double newValue, Uniform theUniform ) {
    glUseProgram ( p );
    const GLfloat valueAsGlData= ( GLfloat ) newValue;
    GLint location=theUniform.getLocation();

    switch ( theUniform.getSize() ) {
    case 1:
        glUniform1f ( location,valueAsGlData );
        break;
    case 2:
        glUniform2f ( location,valueAsGlData,theUniform.getValueAsFloat ( 2 ) );
        break;
    case 3:
        glUniform3f ( location,valueAsGlData,theUniform.getValueAsFloat ( 2 ),theUniform.getValueAsFloat ( 3 ) );
        break;
    case 4:
        glUniform4f ( location,valueAsGlData,theUniform.getValueAsFloat ( 2 ),theUniform.getValueAsFloat ( 3 ),theUniform.getValueAsFloat ( 4 ) );
        break;
    }

    //update the vector
    for ( int i=0;i<uniforms.size();i++ ) {
        if ( theUniform.getName().compare ( uniforms[i].getName() ) ==0 ) {
            uniforms[i].setFloatValue ( newValue,1 );
            //
            break;
        }
    }
}

void SceneBase::applyYvalueChange ( double newValue, Uniform theUniform ) {
    //std::cout<<" Yvalue Changed ";
    glUseProgram ( p );
    const GLfloat valueAsGlData= ( GLfloat ) newValue;
    GLint location=theUniform.getLocation();

    switch ( theUniform.getSize() ) {
    case 2:
        glUniform2f ( location,theUniform.getValueAsFloat ( 1 ),valueAsGlData );
        break;
    case 3:
        glUniform3f ( location,theUniform.getValueAsFloat ( 1 ),valueAsGlData,theUniform.getValueAsFloat ( 3 ) );
        break;
    case 4:
        glUniform4f ( location,theUniform.getValueAsFloat ( 1 ),valueAsGlData,theUniform.getValueAsFloat ( 3 ),theUniform.getValueAsFloat ( 4 ) );
        break;
    }

    //update the vector
    for ( int i=0;i<uniforms.size();i++ ) {
        if ( theUniform.getName().compare ( uniforms[i].getName() ) ==0 ) {
            uniforms[i].setFloatValue ( newValue,2 );
            //std::cout<<"Valor cambiado en "<<i<<" por "<<newValue;
            break;
        }
    }

}

void SceneBase::applyZvalueChange ( double newValue, Uniform theUniform ) {
    glUseProgram ( p );
    const GLfloat valueAsGlData= ( GLfloat ) newValue;
    GLint location=theUniform.getLocation();
    switch ( theUniform.getSize() ) {
    case 3:
        glUniform3f ( location,theUniform.getValueAsFloat ( 1 ),theUniform.getValueAsFloat ( 2 ),valueAsGlData );
        break;
    case 4:
        glUniform4f ( location,theUniform.getValueAsFloat ( 1 ),theUniform.getValueAsFloat ( 2 ),valueAsGlData,theUniform.getValueAsFloat ( 4 ) );
        break;
    }

    //update the vector
    for ( int i=0;i<uniforms.size();i++ ) {
        if ( theUniform.getName().compare ( uniforms[i].getName() ) ==0 ) {
            uniforms[i].setFloatValue ( newValue,3 );
            //std::cout<<"Valor cambiado en "<<i<<" por "<<newValue;
            break;
        }
    }
}

void SceneBase::applyWvalueChange ( double newValue, Uniform theUniform ) {
    glUseProgram ( p );
    const GLfloat valueAsGlData= ( GLfloat ) newValue;
    GLint location=theUniform.getLocation();
    glUniform4f ( location,theUniform.getValueAsFloat ( 1 ),theUniform.getValueAsFloat ( 2 ),theUniform.getValueAsFloat ( 3 ),valueAsGlData );

    //update the vector
    for ( int i=0;i<uniforms.size();i++ ) {
        if ( theUniform.getName().compare ( uniforms[i].getName() ) ==0 ) {
            uniforms[i].setFloatValue ( newValue,4 );
            //std::cout<<"Valor cambiado en "<<i<<" por "<<newValue;
            break;
        }
    }
}

QString SceneBase::readShaderCode ( const QString & name ) const {
    QString text;
    QFile file ( name );
    if ( !file.open ( QIODevice::ReadOnly | QIODevice::Text ) )
        exit ( 1 );

    QTextStream in ( &file );
    text = in.readAll();
    file.close();

    return text;
}
#endif

void SceneBase::setDefaultMaterial ( void ) const {
    GLfloat mat_a[] = {0.1, 0.1, 0.1, 1.0};
    GLfloat mat_d[] = {0.7, 0.7, 0.5, 1.0};
    GLfloat mat_s[] = {1.0, 1.0, 1.0, 1.0};
    GLfloat shine[] = {120.0};

    glMaterialfv ( GL_FRONT_AND_BACK, GL_AMBIENT,   mat_a );
    glMaterialfv ( GL_FRONT_AND_BACK, GL_DIFFUSE,   mat_d );
    glMaterialfv ( GL_FRONT_AND_BACK, GL_SPECULAR,  mat_s );
    glMaterialfv ( GL_FRONT_AND_BACK, GL_SHININESS, shine );
}

void SceneBase::createActions() {
    pointColorDialogAct = new QAction ( tr ( "Point col&or" ), this );
    pointColorDialogAct->setShortcut ( tr ( "Ctrl+L" ) );
    pointColorDialogAct->setStatusTip ( tr ( "Set point color" ) );
    connect ( pointColorDialogAct, SIGNAL ( triggered() ), this, SLOT ( vertexColors() ) );
    pointColorDialogAct->setEnabled ( false );

    openTexAct = new QAction ( tr ( "&Texture" ), this );
    openTexAct->setShortcut ( tr ( "Ctrl+T" ) );
    openTexAct->setStatusTip ( tr ( "Open texture" ) );
    connect ( openTexAct, SIGNAL ( triggered() ), this, SLOT ( textureHandling() ) );
    openTexAct->setEnabled ( false );

    decimateAct = new QAction ( tr ( "&Decimater" ), this );
    decimateAct->setShortcut ( tr ( "Ctrl+D" ) );
    decimateAct->setStatusTip ( tr ( "Decimate scene" ) );
    connect ( decimateAct, SIGNAL ( triggered() ), this, SLOT ( simplifyModel() ) );
    decimateAct->setEnabled ( false );

#ifdef WITH_GLEW
    shaderConfigAct = new  QAction ( tr ( "&ShaderConfig" ), this );
    shaderConfigAct->setShortcut ( tr ( "Ctrl+F" ) );
    shaderConfigAct->setStatusTip ( tr ( "Change Current Shader Parameters" ) );
    connect ( shaderConfigAct, SIGNAL ( triggered() ), this, SLOT ( shaderConfig() ) );
    shaderConfigAct->setEnabled ( false );
#endif

    detachTexAct = new QAction ( tr ( "Detach Textu&re" ), this );
    detachTexAct->setShortcut ( tr ( "Ctrl+U" ) );
    detachTexAct->setStatusTip ( tr ( "Detach texture" ) );
    connect ( detachTexAct, SIGNAL ( triggered() ), this, SLOT ( textureHandling() ) );
    detachTexAct->setEnabled ( false );
    detachTexAct->setVisible ( false );

    drawModesActs = new QActionGroup ( this );

    stripArraysAct = new QAction ( tr ( "Strips" ), drawModesActs );
    stripArraysAct->setCheckable ( true );
    coloredStripsAct = new QAction ( tr ( "Colored Strips" ), drawModesActs );
    coloredStripsAct->setCheckable ( true );
    pointsAct = new QAction ( tr ( "Points" ), drawModesActs );
    pointsAct->setCheckable ( true );
    connect ( pointsAct, SIGNAL ( toggled ( bool ) ), this, SLOT ( points ( bool ) ) );
    wiredAct = new QAction ( tr ( "Wired Frame" ), drawModesActs );
    wiredAct->setCheckable ( true );
    hiddenLineAct = new QAction ( tr ( "hidden Line" ), drawModesActs );
    hiddenLineAct->setCheckable ( true );
    solidFlatAct = new QAction ( tr ( "Solid Flat" ), drawModesActs );
    solidFlatAct->setCheckable ( true );
    solidSmoothAct = new QAction ( tr ( "Solid Smooth" ), drawModesActs );
    solidSmoothAct->setCheckable ( true );
    coloredVerticesAct = new QAction ( tr ( "Colored Vertices" ), drawModesActs );
    coloredVerticesAct->setCheckable ( true );
    solidColoredFacesAct = new QAction ( tr ( "Solid Colored Faces" ), drawModesActs );
    solidColoredFacesAct->setCheckable ( true );
    smoothColoredFacesAct = new QAction ( tr ( "Smooth Colored Faces" ), drawModesActs );
    smoothColoredFacesAct->setCheckable ( true );

    drawModesActs->setEnabled ( false );
    connect ( drawModesActs, SIGNAL ( triggered ( QAction * ) ), this, SLOT ( currentAction ( QAction * ) ) );

    boundingGroupActs = new QActionGroup ( this );
    boundingGroupActs->setExclusive ( false );
    boundingBoxAct = new QAction ( tr ( "Draw &bounding box" ), boundingGroupActs );
    boundingBoxAct->setShortcut ( tr ( "Ctrl+B" ) );
    boundingBoxAct->setCheckable ( true );
    connect ( boundingBoxAct, SIGNAL ( toggled ( bool ) ), this, SLOT ( drawBoundingBox ( bool ) ) );
    boundingSphereAct = new QAction ( tr ( "Draw bounding sp&here" ), boundingGroupActs );
    boundingSphereAct->setShortcut ( tr ( "Ctrl+H" ) );
    boundingSphereAct->setCheckable ( true );
    connect ( boundingSphereAct, SIGNAL ( toggled ( bool ) ), this, SLOT ( drawBoundingSphere ( bool ) ) );

    menu = new QMenu ( tr ( "&Scene" ) );
    menu->addActions ( boundingGroupActs->actions() );
    menu->addSeparator();
    menu->addAction ( openTexAct );
    menu->addAction ( detachTexAct );
    menu->addSeparator();
    menu->addAction ( decimateAct );
    menu->addAction ( shaderConfigAct );
    menu->addSeparator();
    menu->addActions ( drawModesActs->actions() );
}

void SceneBase::vertexColors() {
    QColor color = QColorDialog::getColor ( Qt::white, 0 );
    if ( color.isValid() )
        pointColor = color;
}

void SceneBase::currentAction ( QAction * cA ) {
    if ( cA == stripArraysAct )
        drawMode = StripArrays;
    if ( cA == coloredStripsAct )
        drawMode = ColoredStrips;
    if ( cA == wiredAct )
        drawMode = Wired;
    if ( cA == hiddenLineAct )
        drawMode = HiddenLine;
    if ( cA == solidFlatAct )
        drawMode = SolidFlat;
    if ( cA == solidSmoothAct )
        drawMode = SolidSmooth;
    if ( cA == coloredVerticesAct )
        drawMode = ColoredVertices;
    if ( cA == solidColoredFacesAct )
        drawMode = SolidColoredFaces;
    if ( cA == smoothColoredFacesAct )
        drawMode = SmoothColoredFaces;
    emit changed();
}

void SceneBase::textureHandling() {
    if ( openTexAct->isVisible() ) {
        QString fileName = QFileDialog::getOpenFileName ( 0, "Choose a texture to open", "", "Images (*.bmp *.BMP *.gif *.GIF *.jpg *.JPG *.jpeg *.JPEG *.png *.PNG *.pbm *.PBM *.pgm *.PGM *.ppm *.PPM *.tiff *.TIFF *.xbm *.XBM *.xpm *.XPM)" );
        if ( ! fileName.isNull() ) {
            openTexture ( fileName );
            openTexAct->setVisible ( false );
            openTexAct->setEnabled ( false );
            detachTexAct->setVisible ( true );
            detachTexAct->setEnabled ( true );
            emit changed();
        }
    } else {
        detachTexture();
        detachTexAct->setVisible ( false );
        detachTexAct->setEnabled ( false );
        openTexAct->setVisible ( true );
        openTexAct->setEnabled ( true );
        emit changed();
    }
}
